package drds.server.sqlparser.visitor;

import java.util.List;

import drds.server.sqlparser.sqlobject.SqlObject;
import drds.server.sqlparser.sqlobject.expression.Expression;
import drds.server.sqlparser.sqlobject.expression.InList;
import drds.server.sqlparser.sqlobject.expression.Variable;
import drds.server.sqlparser.sqlobject.expression.binary.BinaryExpression;
import drds.server.sqlparser.sqlobject.expression.binary.BinaryOperator;
import drds.server.sqlparser.sqlobject.expression.data_type.DataType;
import drds.server.sqlparser.sqlobject.expression.data_type.Null;
import drds.server.sqlparser.sqlobject.expression.data_type.Text;
import drds.server.sqlparser.sqlobject.expression.data_type.number.Decimal;
import drds.server.sqlparser.sqlobject.expression.data_type.number.Int;
import drds.server.sqlparser.sqlobject.statement.query.order_by.OrderByItem;

/**
 * 
 * @author 曹正辉<330937205@qq.com>
 * 
 */
public class ParameterizedOutputVisitorUtils {

	public static final String ATTR_PARAMS_SKIP = ".parameterized.skip";

	public static String parameterize(String sql) {
		StringBuilder sb = new StringBuilder();
		ParameterizedVisitor visitor = createParameterizedOutputVisitor(sb);

		// for (int i = 0; i < statementList.size(); i++) {
		// if (i > 0) {
		// sb.append(";\n");
		// }
		// Statement statement = statementList.get(i);
		// statement.accept(visitor);
		// }

		if (visitor.getReplaceCount() == 0) {
			return sql;
		}

		return sb.toString();
	}

	public static ParameterizedVisitor createParameterizedOutputVisitor(Appendable out) {
		return new ParameterizedOutputVisitor(out);
	}

	public static boolean visit(ParameterizedVisitor v, InList x) {
		List<Expression> inItemList = x.getItemList();

		boolean changed = true;
		if (inItemList.size() == 1 && inItemList.get(0) instanceof Variable) {
			changed = false;
		}

		x.getExpression().accept(v);

		if (x.isNot()) {
			v.print(" not in (?)");
		} else {
			v.print(" in (?)");
		}

		if (changed) {
			v.incrementReplaceCunt();
		}

		return false;
	}

	public static boolean visit(ParameterizedVisitor v, Int x) {
		if (!checkParameterize(x)) {
			return OutputVisitorUtils.visit(v, x);
		}

		v.print('?');
		v.incrementReplaceCunt();
		return false;
	}

	public static boolean visit(ParameterizedVisitor v, Decimal x) {
		if (!checkParameterize(x)) {
			return OutputVisitorUtils.visit(v, x);
		}

		v.print('?');
		v.incrementReplaceCunt();
		return false;
	}

	public static boolean visit(ParameterizedVisitor v, Text x) {
		v.print('?');
		v.incrementReplaceCunt();
		return false;
	}

	public static boolean checkParameterize(SqlObject x) {
		if (Boolean.TRUE.equals(x.getAttribute(ParameterizedOutputVisitorUtils.ATTR_PARAMS_SKIP))) {
			return false;
		}

		SqlObject parent = x.getParent();

		if (parent instanceof OrderByItem //
		) {
			return false;
		}

		return true;
	}

	public static boolean visit(ParameterizedVisitor v, Null x) {
		SqlObject parent = x.getParent();
		if (parent instanceof BinaryExpression) {
			BinaryExpression binaryOperate = (BinaryExpression) parent;
			if (binaryOperate.getBinaryOperator() == BinaryOperator.IsNot || binaryOperate.getBinaryOperator() == BinaryOperator.Is) {
				v.print("NULL");
				return false;
			}
		}

		v.print('?');
		v.incrementReplaceCunt();
		return false;
	}

	public static boolean visit(ParameterizedVisitor v, Variable x) {
		v.print('?');
		v.incrementReplaceCunt();
		return false;
	}

	public static BinaryExpression merge(ParameterizedVisitor v, BinaryExpression x) {
		Expression left = x.getLeft();
		Expression right = x.getRight();
		SqlObject parent = x.getParent();

		if (left instanceof DataType && right instanceof DataType) {
			if (x.getBinaryOperator() == BinaryOperator.Equal //
					|| x.getBinaryOperator() == BinaryOperator.NotEqual) {
				if ((left instanceof Int) && (right instanceof Int)) {
					if (((Int) left).getNumber().intValue() < 100) {
						left.putAttribute(ATTR_PARAMS_SKIP, true);
					}
					if (((Int) right).getNumber().intValue() < 100) {
						right.putAttribute(ATTR_PARAMS_SKIP, true);
					}
				} else {
					left.putAttribute(ATTR_PARAMS_SKIP, true);
					right.putAttribute(ATTR_PARAMS_SKIP, true);
				}
			}
			return x;
		}

		for (;;) {
			if (x.getRight() instanceof BinaryExpression) {
				if (x.getLeft() instanceof BinaryExpression) {
					BinaryExpression $left = (BinaryExpression) x.getLeft();
					if ($left.getRight().equals(x.getRight())) {
						x = $left;
						v.incrementReplaceCunt();
						continue;
					}
				}
				Expression mergedRight = merge(v, (BinaryExpression) x.getRight());
				if (mergedRight != x.getRight()) {
					x = new BinaryExpression(x.getLeft(), x.getBinaryOperator(), mergedRight);
					v.incrementReplaceCunt();
				}
				x.setParent(parent);
			}

			break;
		}

		if (x.getLeft() instanceof BinaryExpression) {
			Expression mergedLeft = merge(v, (BinaryExpression) x.getLeft());
			if (mergedLeft != x.getLeft()) {
				x = new BinaryExpression(mergedLeft, x.getBinaryOperator(), x.getRight());
				v.incrementReplaceCunt();
			}
			x.setParent(parent);
		}

		// ID = ? OR ID = ? => ID = ?
		if (x.getBinaryOperator() == BinaryOperator.Or) {
			if ((left instanceof BinaryExpression) && (right instanceof BinaryExpression)) {
				BinaryExpression leftBinary = (BinaryExpression) x.getLeft();
				BinaryExpression rightBinary = (BinaryExpression) x.getRight();

				if (mergeEqual(leftBinary, rightBinary)) {
					v.incrementReplaceCunt();
					return leftBinary;
				}

				if (isLiteral(leftBinary.getLeft()) //
						&& leftBinary.getBinaryOperator() == BinaryOperator.Or) {
					if (mergeEqual(leftBinary.getRight(), right)) {
						v.incrementReplaceCunt();
						return leftBinary;
					}
				}
			}
		}

		return x;
	}

	private static boolean mergeEqual(Expression a, Expression b) {
		if (!(a instanceof BinaryExpression)) {
			return false;
		}
		if (!(b instanceof BinaryExpression)) {
			return false;
		}

		BinaryExpression binaryA = (BinaryExpression) a;
		BinaryExpression binaryB = (BinaryExpression) b;

		if (binaryA.getBinaryOperator() != BinaryOperator.Equal) {
			return false;
		}

		if (binaryB.getBinaryOperator() != BinaryOperator.Equal) {
			return false;
		}

		if (!(binaryA.getRight() instanceof DataType || binaryA.getRight() instanceof Variable)) {
			return false;
		}

		if (!(binaryB.getRight() instanceof DataType || binaryB.getRight() instanceof Variable)) {
			return false;
		}

		return binaryA.getLeft().toString().equals(binaryB.getLeft().toString());
	}

	private static boolean isLiteral(Expression expression) {
		if (expression instanceof DataType) {
			return true;
		}

		if (expression instanceof BinaryExpression) {
			BinaryExpression binaryOperate = (BinaryExpression) expression;
			return isLiteral(binaryOperate.getLeft()) && isLiteral(binaryOperate.getRight());
		}

		return false;
	}
}
